using System;

namespace Dida
{
	/// <summary>
	/// Represents a plane using a <see cref = "normal">normal</see> and 
	/// <see cref = "dist">distance</see> to the origin. These
	/// normal and distance match the values in a standard plane equation:
	/// <c>x * A + y * B + z * C + D = 0</c>
	/// where A, B and C would be the components of the normal, and D the 
	/// distance to the origin.
	/// </summary>
	public struct Plane
	{
		/// <summary>
		/// The normal of this plane. This value should always be normalized.
		/// </summary>
		public Vector3D normal;
		/// <summary>
		/// The shortest distance from the plane to the origin. This value 
		/// can be calculated using the negated <see cref = "Vector3D.DotProduct">
		/// DotProduct</see> between any point on the sphere, and it's normal.
		/// </summary>
		public float dist;

		/// <summary>
		/// Creates a plane with the given parameters.
		/// </summary>
		/// <param name="normal">The normal of the plane</param>
		/// <param name="dist">The signed distance from the plane to 
		/// the origin, in the direction of the normal.</param>
		public Plane(Vector3D normal, float dist)
		{
			this.normal = normal;
			this.dist = dist;
		}

		/// <summary>
		/// <para>
		/// Creates a plane from the triangle formed by the 3 vertices.
		/// </para>
		/// <para>
		/// The vertices are expected to be in clockwise order, when
		/// seen from the front side.
		/// </para>
		/// </summary>
		/// <param name="verts">An array of 3 vertices, forming the triangle.</param>
		public Plane(Vector3D[] verts)
		{
			normal = Vector3D.CrossProduct(verts[1] - verts[0], verts[2] - verts[0]);
			normal.Normalize();
			dist = -Vector3D.DotProduct(verts[0], normal);
		}

		/// <summary>
		/// <para>
		/// Creates a plane from the triangle formed by the 3 vertices.
		/// </para>
		/// <para>
		/// The vertices are expected to be in clockwise order, when
		/// seen from the front side.
		/// </para>
		/// </summary>
		/// <param name="a">The first vertex of the triangle</param>
		/// <param name="b">The second vertex of the triangle</param>
		/// <param name="c">The third vertex of the triangle</param>
		public Plane(Vector3D a, Vector3D b, Vector3D c)
		{
			normal = Vector3D.CrossProduct(b - a, c - a);
			normal.Normalize();
			dist = -Vector3D.DotProduct(a, normal);
		}

		/// <summary>
		/// Calculates the signed distance from the plane to the given point.
		/// If the point is in front of the plane, the distance will be
		/// positive, if it is at the back of the plane, it will be negative.
		/// </summary>
		/// <param name="pt">The point for which the distance should
		/// be calculated</param>
		/// <returns>The distance from the plane to the point</returns>
		public float GetDist(Vector3D pt)
		{
			return Vector3D.DotProduct(pt, normal) + dist;
		}

		/// <summary>
		/// Determines on which side of the plane the given point lays. This
		/// can be <see cref = "PlaneSide.Back">PlaneSide.Back</see>, 
		/// <see cref = "PlaneSide.Front">PlaneSide.Front</see>, and 
		/// <see cref = "PlaneSide.OnPlane">PlaneSide.OnPlane</see>.
		/// PlaneSide.OnPlane is for points that lay on the plane, or 
		/// are very close to it, to take floating point inaccuracy into
		/// account.
		/// </summary>
		/// <param name="pt">The point for which the side should be determined</param>
		/// <returns>A <see cref = "PlaneSide">PlaneSide</see> value, 
		/// that specifies on which of the plane the point lays.</returns>
		public PlaneSide GetSide(Vector3D pt)
		{
			float dist = GetDist(pt);
			if (dist > .001f)
				return PlaneSide.Front;
			else if (dist < -.001f)
				return PlaneSide.Back;
			else
				return PlaneSide.OnPlane;
		}
	}

	/// <summary>
	/// Specifies on which side of a plane a certain point lays.
	/// </summary>
	/// <seealso cref = "Plane"/>
	public enum PlaneSide
	{
		/// <summary>
		/// The point lays in front of the plane. The front side
		/// of the plane is the side the normal is pointing to.
		/// </summary>
		Front,
		/// <summary>
		/// The point lays on the back side of the plane.
		/// </summary>
		Back,
		/// <summary>
		/// The point lays on the plane (or is very close to it, 
		/// when take floating point inaccuracy into account).
		/// </summary>
		OnPlane,
	}
}